
// Copyright (C) 2009-2011 Luca Piccioni
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//   
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

using Derm.OpenGL;
using Derm.Raster;

namespace Derm.Render
{
	/// <summary>
	/// Texture.
	/// </summary>
	/// <remarks>
	/// <para>
	/// Textures are object holding a set of data, that can be accessed within a shader using a set of coordinates. Textures classes
	/// derives from <see cref="Texture"/>, which define a base implementation without specifying the texture dimensionality.
	/// </para>
	/// <para>
	/// Texture can be one dimensional (<see cref="Texture1d"/>), two dimensional (<see cref="Texture2d"/>), three dimensional
	/// (<see cref="Texture3d"/>), cube mapped (<see cref="TextureCube"/>).
	/// </para>
	/// </remarks>
	[DebuggerDisplay("Texture Pixel:{mPixelFormat}")]
	public abstract class Texture : RenderResource
	{
		#region Internal Format Management

		/// <summary>
		/// Texture pixel format.
		/// </summary>
		/// <remarks>
		/// 
		/// </remarks>
		public Pixel.Type PixelFormat
		{
			get { return (mPixelFormat); }
			set {
				// Store pixel format
				mPixelFormat = value;
				// Check for swizzle
				SetupTextelSwizzle();
			}
		}

		/// <summary>
		/// Setup texture textel components swizzle.
		/// </summary>
		/// <remarks>
		/// Texture swizzle is a way to set/clone/mix textel components during the shaders execution. It
		/// is mainly used for grayscale textures, cloning the R component in the G and B components;
		/// sometimes is used to ignore A components, since the data specify it by it is not meaninfull.
		/// </remarks>
		private void SetupTextelSwizzle()
		{
			if (RenderContext.CurrentCaps.TextureSwizzle.Supported == true) {
				switch (mPixelFormat) {

					#region GRAY Internal Formats

					case Pixel.Type.GRAY8:
					case Pixel.Type.GRAY16:
					case Pixel.Type.GRAY32:
					case Pixel.Type.GRAYF:
					case Pixel.Type.GRAYHF:
						// Emulated Gl.LUMINANCE textel format
						mTextelSwizzleRGBA = new int[] {
							Gl.RED,
							Gl.RED,
							Gl.RED,
							Gl.ONE
						};
						break;

					#endregion

					default:
						// By default, no swizzle is required
						mTextelSwizzleRGBA = null;
						break;
				}
			}
		}

		/// <summary>
		/// Pixel format.
		/// </summary>
		/// <remarks>
		/// This member determine the texture internal format.
		/// </remarks>
		private Pixel.Type mPixelFormat = Pixel.Type.None;

		/// <summary>
		/// Texture textel swizzle setup.
		/// </summary>
		/// <remarks>
		/// When this member is set to null, not textel components swizzle
		/// is required.
		/// </remarks>
		private int[] mTextelSwizzleRGBA = null;

		/// <summary>
		/// Texture textel alpha swizzle setup.
		/// </summary>
		/// <remarks>
		/// When this member is set dst -1, not textel components swizzle
		/// is required.
		/// </remarks>
		private int mTextelSwizzleA = -1;

		#endregion

		#region Texture Data Storage

		/// <summary>
		/// Determine whether defined Texture data shall be released after having
		/// defined this Texture.
		/// </summary>
		/// <remarks>
		/// <para>
		/// By default, this property is set to false.
		/// </para>
		/// </remarks>
		public bool AutoReleaseData
		{
			get { return (mAutoRelease); }
			set {
				mAutoRelease = value;
			}
		}

		/// <summary>
		/// Release texture data.
		/// </summary>
		/// <remarks>
		/// <para>
		/// If the current OpenGL implementation  supports texture objects, it is possible to release host texture data,
		/// since it is managed by OpenGL. To avoid unnecessary data duplication, Texture implementation shall release
		/// the textel data, but retaining those information usefull for further texture operations.
		/// </para>
		/// <para>
		/// Regarding <see cref="TextureData"/>, only the <see cref="TextureData.Data"/> object shall be set null. Other
		/// properties are used for Texture management (<see cref="ReleaseData(IEnumerable&lt;TextureData&gt;)"/>. Each derived
		/// class defining a derived TextureData, shall manage the derived class properties by overriding this method.
		/// </para>
		/// </remarks>
		protected abstract void ReleaseData();

		/// <summary>
		/// Release a generic enumeration of TextureData.
		/// </summary>
		/// <param name="dataList">
		/// An enumerable of <see cref="TextureData"/> listing all <see cref="TextureData"/>
		/// instances to release.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="dataList"/> is null.
		/// </exception>
		protected static void ReleaseData(IEnumerable<TextureData> dataList)
		{
			if (dataList == null)
				throw new ArgumentNullException("dataList");

			foreach (TextureData data in dataList) {
				// Reset textel data
				data.Data = null;
			}
		}

		/// <summary>
		/// Generic texture data description.
		/// </summary>
		protected class TextureData
		{
			/// <summary>
			/// The object able to define <see cref="Data"/>, <see cref="TextelFormat"/> and <see cref="Stride"/>.
			/// </summary>
			/// <remarks>
			/// <para>
			/// This member is used for special purpose operation, that requires additional information about the
			/// texture data.
			/// </para>
			/// <para>
			/// Actually recognized objects are:
			/// - <see cref="Image"/> objects
			/// 
			/// Other objects can be set on this field, but they won't be recognized for those operations.
			/// </para>
			/// </remarks>
			public object Source = null;

			/// <summary>
			/// Texture data.
			/// </summary>
			public object Data = null;

			/// <summary>
			/// Texture data format.
			/// </summary>
			public Pixel.Type TextelFormat = Pixel.Type.None;

			/// <summary>
			/// Texture array width.
			/// </summary>
			public uint Width = 0;

			/// <summary>
			/// Texture array height.
			/// </summary>
			public uint Height = 0;

			/// <summary>
			/// Scanline width, in bytes.
			/// </summary>
			public uint Stride = 0;

			/// <summary>
			/// Mipmap level of this texture data.
			/// </summary>
			public int MipmapLevel = 0;
		}

		/// <summary>
		/// Define an empty Texture data.
		/// </summary>
		/// <param name="format">
		/// A <see cref="Pixel.Type"/> that specifies the texture internal format.
		/// </param>
		/// <param name="data">
		/// A <see cref="TextureData"/> which will be filled with the information.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception throw if <paramref name="data"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="format"/> equals to <see cref="Pixel.Type.None"/>.
		/// </exception>
		protected void DefineTextureData(Pixel.Type format, TextureData data)
		{
			if (data == null)
				throw new ArgumentNullException("data");
			if (format == Pixel.Type.None)
				throw new ArgumentException("invalid pixel format", "format");

			data.Source = null;
			data.Data = null;
			data.TextelFormat = format;
			data.Stride = 0;
			data.MipmapLevel = 0;
		}

		/// <summary>
		/// Define a TextureData from <see cref="Derm.Raster.Image"/>.
		/// </summary>
		/// <param name="image">
		/// A <see cref="Derm.Raster.Image"/> defining a texture data.
		/// </param>
		/// <param name="data">
		/// A <see cref="TextureData"/> which will be filled with the information.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="image"/> is null.
		/// </exception>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="data"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="image"/> is not allocated.
		/// </exception>
		/// <seealso cref="Derm.Raster.Image"/>
		protected void DefineTextureData(Image image, TextureData data)
		{
			if (image == null)
				throw new ArgumentNullException("image");
			if (data == null)
				throw new ArgumentNullException("data");
			if (image.GetPixelData() == null)
				throw new ArgumentException("not allocated", "image");

			data.Source = image;
			data.Data = image.GetPixelData();
			data.TextelFormat = image.PixelFormat;
			data.Stride = image.Stride;
			data.MipmapLevel = 0;
		}

		/// <summary>
		/// Flag for releasing data on upload.
		/// </summary>
		private bool mAutoRelease = true;

		#endregion

		#region Textel Management

		/// <summary>
		/// Texture width.
		/// </summary>
		/// <remarks>
		/// Every texture target has a width.
		/// </remarks>
		public abstract uint Width { get; }

		/// <summary>
		/// Texture width.
		/// </summary>
		/// <remarks>
		/// Every texture target has a height, except:
		/// - Texture1d: this target has an height always equals to 1
		/// </remarks>
		public abstract uint Height { get; }

		/// <summary>
		/// Texture depth.
		/// </summary>
		/// <remarks>
		/// Only Texture3d target has a depth. For every else texture target, it is set to 1.
		/// </remarks>
		public abstract uint Depth { get; }

		/// <summary>
		/// Check whether the texture extents are compatible with context capabilities.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> determining the underlying texture capabilities.
		/// </param>
		/// <param name="width">
		/// A <see cref="System.UInt32"/> that specifies the texture width.
		/// </param>
		/// <param name="height">
		/// A <see cref="System.UInt32"/> that specifies the texture height.
		/// </param>
		/// <param name="depth">
		/// A <see cref="System.UInt32"/> that specifies the texture depth.
		/// </param>
		/// <param name="format">
		/// A <see cref="Pixel.Type"/> that specifies the texture internal format.
		/// </param>
		/// <exception cref="ArgumentNullException">
		/// Exception throw if <paramref name="ctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="width"/>, <paramref name="height"/> or <paramref name="depth"/> is greater than
		/// the maximum allowed for the specific texture target.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if NPOT texture are not supported by <paramref name="ctx"/>, and <paramref name="width"/>, <paramref name="height"/>
		/// or <paramref name="depth"/> is not a power-of-two value.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="format"/> is not a supported internal format.
		/// </exception>
		protected void CheckCapabilities(RenderContext ctx, uint width, uint height, uint depth, Pixel.Type format)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
		
			RenderContext.Capabilities caps = ctx.Caps;

			// Texture maximum size
			switch (TextureTarget) {
				case Gl.TEXTURE_1D:
					if (width > caps.MaxTexture2DSize)
						throw new ArgumentException(String.Format("width greater than maximum allowed ({0})", caps.MaxTexture2DSize));
					break;
				case Gl.TEXTURE_2D:
					if (width > caps.MaxTexture2DSize)
						throw new ArgumentException(String.Format("width greater than maximum allowed ({0})", caps.MaxTexture2DSize));
					if (height > caps.MaxTexture2DSize)
						throw new ArgumentException(String.Format("height greater than maximum allowed ({0})", caps.MaxTexture2DSize));
					break;
				case Gl.TEXTURE_RECTANGLE:
					if (width > caps.MaxTextureRectSize)
						throw new ArgumentException(String.Format("width greater than maximum allowed ({0})", caps.MaxTextureRectSize));
					if (height > caps.MaxTextureRectSize)
						throw new ArgumentException(String.Format("height greater than maximum allowed ({0})", caps.MaxTextureRectSize));
					break;
				case Gl.TEXTURE_3D:
					if (width > caps.MaxTexture3DSize)
						throw new ArgumentException(String.Format("width greater than maximum allowed ({0})", caps.MaxTexture3DSize));
					if (height > caps.MaxTexture3DSize)
						throw new ArgumentException(String.Format("height greater than maximum allowed ({0})", caps.MaxTexture3DSize));
					if (depth > caps.MaxTexture3DSize)
						throw new ArgumentException(String.Format("depth greater than maximum allowed ({0})", caps.MaxTexture3DSize));
					break;
				case Gl.TEXTURE_CUBE_MAP:
					if (width > caps.MaxTextureCubeSize)
						throw new ArgumentException(String.Format("width greater than maximum allowed ({0})", caps.MaxTextureCubeSize));
					if (height > caps.MaxTextureCubeSize)
						throw new ArgumentException(String.Format("height greater than maximum allowed ({0})", caps.MaxTextureCubeSize));
					break;
				default:
					throw new NotImplementedException("not implemented checks on texture " + GetType().ToString());
			}
			// Texture not-power-of-two
			if (caps.TextureNPOT.Supported == false) {
				if (IsPowerOfTwo(width) == false)
					throw new ArgumentException(String.Format("NPOT texture width not supported (width is {0})", width));
				if (IsPowerOfTwo(height) == false)
					throw new ArgumentException(String.Format("NPOT texture height not supported (height is {0})", height));
				if (IsPowerOfTwo(depth) == false)
					throw new ArgumentException(String.Format("NPOT texture depth not supported (height is {0})", height));
			}
			// Texture internal format
			if (Pixel.IsSupportedInternalFormat(format, ctx) == false)
				throw new ArgumentException(String.Format("not supported texture internal format {0}", format), "format");
		}

		/// <summary>
		/// Determine whether an integer is a power-of-two value.
		/// </summary>
		/// <param name="x">
		/// A <see cref="System.UInt32"/> that specify the value to test.
		/// </param>
		/// <returns>
		/// It returns a boolean value indicating whether <paramref name="x"/> is a power-of-two value.
		/// </returns>
		private bool IsPowerOfTwo(uint x)
		{
			return (x & (x - 1)) == 0;
		}

		/// <summary>
		/// Download Texture data to an Image instance.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for downloading texture data.
		/// </param>
		/// <param name="pType">
		/// A <see cref="Pixel.Type"/> determining the pixel format of the downloaded data.
		/// </param>
		/// <param name="target">
		/// A <see cref="System.Int32"/> that specifies the texture target.
		/// </param>
		/// <returns>
		/// 
		/// </returns>
		protected Image[] Get(RenderContext ctx, Pixel.Type pType, int target)
		{
			Image image;
			int format = Pixel.GetGlFormat(pType);
			int type = Pixel.GetGlDataFormat(pType);
			int width, height;

			// Bind this Texture
			Bind(ctx);

			// Get texture extents
			Gl.GetTexLevelParameter(TextureTarget, 0, Gl.TEXTURE_WIDTH, out width);
			Debug.Assert(RenderException.CheckErrors() == true);
			Gl.GetTexLevelParameter(TextureTarget, 0, Gl.TEXTURE_HEIGHT, out height);
			Debug.Assert(RenderException.CheckErrors() == true);
			if ((width <= 0) || (height <= 0))
				throw new InvalidOperationException(String.Format("invalid texture extents {0}x{1}", width, height));

			// Create image
			image = Image.CreateImage((uint)width, (uint)height, pType);

			// Set pixel transfer
			foreach (int alignment in new int[] { 8, 4, 2, 1 }) {
				if (image.Stride % alignment == 0) {
					Gl.PixelStore(Gl.PACK_ALIGNMENT, alignment);
					Debug.Assert(RenderException.CheckErrors() == true);
					break;
				}
			}

			// Download texture contents
			Gl.GetTexImage(target, 0, format, type, image.GetPixelData());
			Debug.Assert(RenderException.CheckErrors() == true);

			// Unbind this texture
			Unbind(ctx);

			return (new Image[] { image });
		}

		#endregion

		#region Mipmapping

		/// <summary>
		/// Request the creation of this Texture mipmaps.
		/// </summary>
		public void RequestMipmapsCreation()
		{
			
		}

		/// <summary>
		/// Generate mipmaps for this Texture.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for generating texture mipmaps.
		/// </param>
		internal virtual void GenerateMipmaps(RenderContext ctx)
		{
			GenerateMipmaps(ctx, TextureTarget);
		}

		/// <summary>
		/// Generate mipmaps for this Texture, replacing mipmaps level from 1 to <see cref="MipmapLevels"/> - 1.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for mipmapping definition.
		/// </param>
		/// <param name="target">
		/// A <see cref="System.Int32"/> indicating the target for generating
		/// bitmaps.
		/// </param>
		protected void GenerateMipmaps(RenderContext ctx, int target)
		{
			// Bind this Texture
			Bind(ctx);
			// Generate mipmaps
			Gl.GenerateMipmap(target);
			Debug.Assert(RenderException.CheckErrors() == true);
			// Unbind this texture
			Unbind(ctx);

			// Now texture has mipmaps
			mHasMipMaps = true;
		}

		/// <summary>
		/// Flag indicating whether this Texture has mipmaps definition.
		/// </summary>
		/// <remarks>
		/// This flag specify whether this Texture has mipmaps, or not. By
		/// </remarks>
		public bool HasMipmaps
		{
			get { return (mHasMipMaps); }
			protected set { mHasMipMaps = value; }
		}

		/// <summary>
		/// Texture mapmaps level count (including the base level).
		/// </summary>
		/// <remarks>
		/// <para>
		/// If this texture is not defined, this property shall return 0.
		/// </para>
		/// <para>
		/// If texture target doesn't support mipmapping, this property shall return 1, if it
		/// is defined.
		/// </para>
		/// </remarks>
		protected virtual uint MipmapLevels
		{
			get {
				uint maxSize = Math.Max(Math.Max(Width, Height), Depth);

				if (maxSize > 0)
					return ((uint)Math.Log((double)maxSize, 2.0));
				else
					return (0);
			}
		}

		/// <summary>
		/// Compute the mipmap for a specific level.
		/// </summary>
		/// <param name="level">
		/// A <see cref="System.UInt32"/> that specify the mipmap level.
		/// </param>
		/// <param name="width">
		/// A <see cref="System.UInt32"/> that specify the computed mipmap level width.
		/// </param>
		/// <param name="height">
		/// A <see cref="System.UInt32"/> that specify the computed mipmap level height.
		/// </param>
		/// <param name="depth">
		/// A <see cref="System.UInt32"/> that specify the computed mipmap level depth.
		/// </param>
		/// <exception cref="ArgumentException">
		/// Exception throw if <paramref name="level"/> is greater than <see cref="MipmapLevels"/>.
		/// </exception>
		/// <exception cref="InvalidOperationException">
		/// Exception thrown if this Texture extents are not defined (i.e. <see cref="Width"/> is 0).
		/// </exception>
		protected void GetMipmapLevelSize(uint level, out uint width, out uint height, out uint depth)
		{
			if (level >= MipmapLevels)
				throw new ArgumentException("greater or equals to MipmapLevels");
			if ((Width == 0) || (Height == 0) || (Depth == 0))
				throw new InvalidOperationException("texture size no defined");

			if (Width > 1)
				width = (uint)Math.Max((double)Width, Math.Pow(2.0, level));
			else
				width = 1;

			if (Height > 1)
				height = (uint)Math.Max((double)Height, Math.Pow(2.0, level));
			else
				height = 1;

			if (Depth > 1)
				depth = (uint)Math.Max((double)Depth, Math.Pow(2.0, level));
			else
				depth = 1;
		}

		/// <summary>
		/// Flag indicating whether this Texture has mipmaps defines.
		/// </summary>
		private bool mHasMipMaps = false;

		#endregion

		#region Filter & Wrapping

		/// <summary>
		/// Texture filter.
		/// </summary>
		public enum Filter
		{
			/// <summary>
			/// Keep the nearest pixel.
			/// </summary>
			Nearest,
			/// <summary>
			/// Linearly interpolate pixels in neightboor.
			/// </summary>
			Linear
		}

		/// <summary>
		/// Texture wrapping mode.
		/// </summary>
		public enum Wrap
		{
			/// <summary>
			/// Texture coordinates are clamped to the valid range.
			/// </summary>
			Clamp,
			/// <summary>
			/// Texture coordinates are wrapped around the valid range.
			/// </summary>
			Repeat,
			/// <summary>
			/// Texture coordinates are wrapped around the valid range, but mirrored.
			/// </summary>
			MirroredRepeat
		}
		
		/// <summary>
		/// Minification filter property.
		/// </summary>
		public Filter MinFilter
		{
			get { return (mMinFilter); }
			set {
				mMinFilter = value;
			}
		}
		
		/// <summary>
		/// Magnification filter property.
		/// </summary>
		public Filter MagFilter
		{
			get { return (mMagFilter); }
			set {
				mMagFilter = value;
			}
		}

		/// <summary>
		/// Wrapping on texture S coordinate.
		/// </summary>
		public Wrap WrapCoordS
		{
			get { return (mWrapS); }
			set {
				mWrapS = value;
			}
		}

		/// <summary>
		/// Wrapping on texture T coordinate.
		/// </summary>
		public Wrap WrapCoordT
		{
			get { return (mWrapT); }
			set {
				mWrapT = value;
			}
		}

		/// <summary>
		/// Apply this Texture parameters.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for setting texture parameter.
		/// </param>
		/// <remarks>
		/// Tipically this routine is implemented by simply calling the routine
		/// <see cref="ApplyParameters(RenderContext,int)"/>.
		/// </remarks>
		public virtual void ApplyParameters(RenderContext ctx)
		{
			ApplyParameters(ctx, TextureTarget);
		}

		/// <summary>
		/// Apply Texture parameters of specific target.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for setting texture parameter.
		/// </param>
		/// <param name="target">
		/// A <see cref="System.Int32"/> that specifies the Texture target.
		/// </param>
		protected void ApplyParameters(RenderContext ctx, int target)
		{
			int iParam;

			#region Minification Filter

			if (mHasMipMaps == true) {
				switch (MinFilter) {
				case Filter.Nearest:
					iParam = Gl.NEAREST_MIPMAP_LINEAR;
					break;
				case Filter.Linear:
				default:
					iParam = Gl.LINEAR_MIPMAP_LINEAR;
					break;
				}

				// Set minification filter (without mipmaps)
				Gl.TexParameter(target, Gl.TEXTURE_MIN_FILTER, iParam);
				Debug.Assert(RenderException.CheckErrors() == true);
			} else {
				switch (MinFilter) {
				case Filter.Nearest:
					iParam = Gl.NEAREST;
					break;
				case Filter.Linear:
				default:
					iParam = Gl.LINEAR;
					break;
				}
				// Set minification filter (without mipmaps)
				Gl.TexParameter(target, Gl.TEXTURE_MIN_FILTER, iParam);
				Debug.Assert(RenderException.CheckErrors() == true);
			}

			#endregion

			#region Magnification Filter

			switch (MagFilter) {
			case Filter.Nearest:
				iParam = Gl.NEAREST;
				break;
			case Filter.Linear:
			default:
				iParam = Gl.LINEAR;
				break;
			}

			// Set minification filter (without mipmaps)
			Gl.TexParameter(target, Gl.TEXTURE_MAG_FILTER, iParam);
			Debug.Assert(RenderException.CheckErrors() == true);

			#endregion

			#region Wrapping

			if (target != Gl.TEXTURE_RECTANGLE) {
				// Wrap S coordinate
				Gl.TexParameter(target, Gl.TEXTURE_WRAP_S, GetWrapParameter(WrapCoordS));
				Debug.Assert(RenderException.CheckErrors() == true);
				// Wrap T coordinate
				Gl.TexParameter(target, Gl.TEXTURE_WRAP_T, GetWrapParameter(WrapCoordT));
				Debug.Assert(RenderException.CheckErrors() == true);
			}

			#endregion

			#region Textel Components Swizzle

			if ((mTextelSwizzleRGBA != null) && (ctx.Caps.TextureSwizzle.Supported == true)) {
				// Set components swizzle setup
				Gl.TexParameter(target, Gl.TEXTURE_SWIZZLE_RGBA, mTextelSwizzleRGBA);
				Debug.Assert(RenderException.CheckErrors() == true);
			}
			if ((mTextelSwizzleA != -1) && (ctx.Caps.TextureSwizzle.Supported == true)) {
				// Set components swizzle setup
				Gl.TexParameter(target, Gl.TEXTURE_SWIZZLE_A, mTextelSwizzleA);
				Debug.Assert(RenderException.CheckErrors() == true);
			}

			#endregion
		}

		/// <summary>
		/// Get wrap paremeter from Wrap.
		/// </summary>
		/// <param name="param">
		/// A <see cref="Wrap"/> indicating the wrap mode.
		/// </param>
		/// <returns>
		/// It returns an integer representing the OpenGL enumeration value
		/// corresponding to the texture wrap mode <paramref name="param"/>.
		/// </returns>
		private static int GetWrapParameter(Wrap param)
		{
			switch (param) {
			case Wrap.Clamp:
				return (Gl.CLAMP_TO_EDGE);
			case Wrap.Repeat:
			default:
				return (Gl.REPEAT);
			case Wrap.MirroredRepeat:
				return (Gl.MIRRORED_REPEAT);
			}
		}

		/// <summary>
		/// Minification  filter.
		/// </summary>
		protected Filter mMinFilter = Filter.Linear;
		/// <summary>
		/// Magnification filter.
		/// </summary>
		protected Filter mMagFilter = Filter.Linear;
		/// <summary>
		/// Wrapping on S coordinate.
		/// </summary>
		protected Wrap mWrapS = Wrap.Repeat;
		/// <summary>
		/// Wrapping on S coordinate.
		/// </summary>
		protected Wrap mWrapT = Wrap.Repeat;
		
		#endregion

		#region Texture Binding

		/// <summary>
		/// Determine the derived Texture target.
		/// </summary>
		/// <remarks>
		/// In the case a this Texture is defined by multiple targets (i.e. cube map textures), this property
		/// shall returns always 0.
		/// </remarks>
		public abstract int TextureTarget { get; }

		/// <summary>
		/// Determine whether this Texture has a valid target.
		/// </summary>
		public bool HasValidTarget { get { return (TextureTarget != 0); } }

		/// <summary>
		/// Bind this Texture.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for binding this Texture.
		/// </param>
		/// <remarks>
		/// To call this routine the condition <i>IsCreated</i> shall be true.
		/// </remarks>
		public virtual void Bind(RenderContext ctx)
		{
			Bind(ctx, TextureTarget);
		}
		
		/// <summary>
		/// Unbind this Texture.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for binding this Texture.
		/// </param>
		/// <remarks>
		/// To call this routine the condition <i>IsCreated</i> shall be true.
		/// </remarks>
		public virtual void Unbind(RenderContext ctx)
		{
			Unbind(ctx, TextureTarget);
		}

		/// <summary>
		/// Bind this Texture on specific target.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for binding this Texture.
		/// </param>
		/// <param name="target">
		/// A <see cref="System.Int32"/> that specifies the texture target. It could be one of the following:
		/// - Gl.TEXTURE_1D
		/// - Gl.TEXTURE_2D
		/// - Gl.TEXTURE_RECTANGLE
		/// - Gl.TEXTURE_3D
		/// - Gl.TEXTURE_CUBE_MAP
		/// </param>
		protected void Bind(RenderContext ctx, int target)
		{
			// Bind this Texture on specified target
			Gl.BindTexture(target, ObjectName);
			Debug.Assert(RenderException.CheckErrors() == true);
		}

		/// <summary>
		/// Unbind this Texture on specific target.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for binding this Texture.
		/// </param>
		/// <param name="target">
		/// A <see cref="System.Int32"/> that specifies the texture target. It could be one of the following:
		/// - Gl.TEXTURE_1D
		/// - Gl.TEXTURE_2D
		/// - Gl.TEXTURE_RECTANGLE
		/// - Gl.TEXTURE_3D
		/// - Gl.TEXTURE_CUBE_MAP
		/// </param>
		protected void Unbind(RenderContext ctx, int target)
		{
			// Unbind this Texture on specified target
			Gl.BindTexture(target, 0);
			Debug.Assert(RenderException.CheckErrors() == true);
		}

		#endregion

		#region Shader Uniform Binding

		/// <summary>
		/// Uniform sampler type for managing this Texture.
		/// </summary>
		internal abstract int SamplerType { get; }

		#endregion

		#region RenderResource Overrides

		/// <summary>
		/// Texture object class.
		/// </summary>
		internal static readonly Guid TextureObjectClass = new Guid("0B0FA1F6-76B2-4B6A-B2B2-319D9E330FB1");

		/// <summary>
		/// Texture object class.
		/// </summary>
		public override Guid ObjectClass { get { return (TextureObjectClass); } }

		/// <summary>
		/// Determine whether this Texture really exists for a specific context.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> that would have created (or a sharing one) the object. This context shall be current to
		/// the calling thread.
		/// </param>
		/// <returns>
		/// It returns a boolean value indicating whether this Texture exists in the object space of <paramref name="ctx"/>.
		/// </returns>
		/// <remarks>
		/// <para>
		/// The object existence is done by checking a valid object by its name <see cref="IRenderResource.ObjectName"/>. This routine will test whether
		/// <paramref name="ctx"/> has created this Texture (or is sharing with the creator).
		/// </para>
		/// </remarks>
		/// <exception cref="ArgumentNullException">
		/// Exception thrown if <paramref name="ctx"/> is null.
		/// </exception>
		/// <exception cref="ArgumentException">
		/// Exception thrown if <paramref name="ctx"/> is not current to the calling thread.
		/// </exception>
		public override bool Exists(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");
			if (ctx.IsCurrent() == false)
				throw new ArgumentException("not current", "ctx");
		
			// Object name space test (and 'ctx' sanity checks)
			if (base.Exists(ctx) == false)
				return (false);

			return (Gl.IsTexture(ObjectName));
		}

		/// <summary>
		/// Create this Texture.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating this object.
		/// </param>
		/// <remarks>
		/// <para>
		/// Texture instances can be created by a <see cref="RenderCreatorService"/> if all the following conditions
		/// are satisfied:
		/// - A <see cref="RenderCreatorService"/> is bound to the object name space of <paramref name="ctx"/>;
		/// - The context <paramref name="ctx"/> is sharing Texture objects.
		/// </para>
		/// </remarks>
		public override void Create(RenderContext ctx)
		{
			if (ctx == null)
				throw new ArgumentNullException("ctx");

			RenderCreatorService creatorService = RenderCreatorService.GetService(ctx.ObjectNameSpace);

			if ((creatorService != null) && (creatorService.IsCurrent == false) && (ctx.IsSharedObjectClass(TextureObjectClass) == true)) {
				
				// Note: the following conditions are satisfied

				creatorService.EnqueueCreationTask(this);
				return;
			} else {

				// Note: creator service doesn't exists, or this thread is the creator service. Actually
				// create this texture.

				base.Create(ctx);
			}
		}

		/// <summary>
		/// Create a Texture name.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating this object name.
		/// </param>
		/// <returns>
		/// It returns a valid object name for this Texture.
		/// </returns>
		protected override uint CreateName(RenderContext ctx)
		{
			uint name;

			// Generate texture name
			Gl.GenTextures(1, out name);
			RenderException.CheckErrors();
			
			return (name);
		}

		/// <summary>
		/// Delete a Texture name.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for deleting this object name.
		/// </param>
		/// <param name="name">
		/// A <see cref="System.UInt32"/> that specifies the object name to delete.
		/// </param>
		protected override void DeleteName(RenderContext ctx, uint name)
		{
			// Delete texture name
			Gl.DeleteTextures(1, new uint[] { name });
			RenderException.CheckErrors();
		}

		/// <summary>
		/// Actually create this RenderResource resources.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for allocating resources.
		/// </param>
		protected override void CreateObject(RenderContext ctx)
		{
			Bind(ctx);
			Unbind(ctx);
		}

		#endregion
	}
}
